import { FBXLoader, type GLTFLoader, OBJLoader, PCDLoader, PLYLoader, SVGLoader } from 'three/examples/jsm/Addons.js';
import {
  Group,
  type LoadingManager,
  Mesh,
  MeshBasicMaterial,
  MeshStandardMaterial,
  type Object3D,
  Points,
  PointsMaterial,
  ShapeGeometry,
} from 'three/webgpu';

const readGLTFFile = (file: File, loader: GLTFLoader, reader: FileReader): Promise<Object3D> => {
  return new Promise((resolve, reject) => {
    reader.addEventListener(
      'load',
      async (event) => {
        const contents = event.target?.result as ArrayBuffer;
        loader.parse(
          contents,
          '',
          (result) => {
            const scene = result.scene;
            scene.name = file.name;
            scene.animations.push(...result.animations);
            loader.dracoLoader?.dispose();
            loader.ktx2Loader?.dispose();
            resolve(scene);
          },
          reject,
        );
      },
      false,
    );
    reader.readAsArrayBuffer(file);
  });
};

const readFBXFile = (file: File, reader: FileReader, manager?: LoadingManager): Promise<Object3D> => {
  return new Promise((resolve) => {
    reader.addEventListener(
      'load',
      async () => {
        const contents = reader.result;
        if (contents === null) return;
        const loader = new FBXLoader(manager);
        const object = loader.parse(contents, '');
        resolve(object);
      },
      false,
    );
    reader.readAsArrayBuffer(file);
  });
};

const readSVGFile = (file: File, reader: FileReader): Promise<Group> => {
  return new Promise((resolve) => {
    reader.addEventListener(
      'load',
      async () => {
        const contents = reader.result;
        if (contents === null) return;
        const loader = new SVGLoader();
        const paths = loader.parse(contents as string).paths;
        const group = new Group();
        group.name = file.name;
        group.scale.multiplyScalar(0.1);
        group.scale.y *= -1;
        for (let i = 0; i < paths.length; i++) {
          const path = paths[i];
          const material = new MeshBasicMaterial({
            color: path.color,
            depthWrite: false,
          });
          const shapes = SVGLoader.createShapes(path);
          for (let j = 0; j < shapes.length; j++) {
            const shape = shapes[j];
            const geometry = new ShapeGeometry(shape);
            const mesh = new Mesh(geometry, material);
            group.add(mesh);
          }
        }
        resolve(group);
      },
      false,
    );
    reader.readAsText(file);
  });
};

const readPLYFile = (file: File, reader: FileReader): Promise<Mesh | Points> => {
  return new Promise((resolve) => {
    reader.addEventListener(
      'load',
      async () => {
        const contents = reader.result;
        if (contents === null) return;
        const geometry = new PLYLoader().parse(contents);
        let object: Mesh | Points;
        if (geometry.index !== null) {
          const material = new MeshStandardMaterial();
          object = new Mesh(geometry, material);
        } else {
          const material = new PointsMaterial({ size: 0.01 });
          material.vertexColors = geometry.hasAttribute('color');
          object = new Points(geometry, material);
        }
        object.name = file.name;
        resolve(object);
      },
      false,
    );
    reader.readAsArrayBuffer(file);
  });
};

const readPCDFile = (file: File, reader: FileReader): Promise<Points> => {
  return new Promise((resolve) => {
    reader.addEventListener(
      'load',
      async () => {
        const contents = reader.result;
        if (contents === null) return;
        const points = new PCDLoader().parse(contents);
        points.name = file.name;
        resolve(points);
      },
      false,
    );
    reader.readAsArrayBuffer(file);
  });
};

const readOBJFile = (file: File, reader: FileReader): Promise<Object3D> => {
  return new Promise((resolve) => {
    reader.addEventListener(
      'load',
      async () => {
        const contents = reader.result as string;
        const object = new OBJLoader().parse(contents);
        object.name = file.name;
        resolve(object);
      },
      false,
    );
    reader.readAsText(file);
  });
};

export { readGLTFFile, readFBXFile, readSVGFile, readPLYFile, readPCDFile, readOBJFile };
